package org.codetranslators.compiler.abstractassembly;

import java.util.Stack;
import java.util.Vector;

import org.codetranslators.common.Label;
import org.codetranslators.common.Temp;

public class AssemCallExp extends AssemExp
{
	private AssemExp func;
	private AssemExpList args;
	private boolean hasReturnValue;
	
	public AssemCallExp(AssemExp f,  AssemExpList a, boolean hasReturnValue) 
	{
		func = f;
		args = a;
		this.hasReturnValue = hasReturnValue;
	}
	
	public AssemExpList children() 
	{
		return new AssemExpList(func,args);
	}
	
	public AssemExp getFunction()
	{
		return func;
	}
	
	public AssemExpList getArgs()
	{
		return args;
	}
	
	public int getType()
	{
		return AssemType.INT_TYPE;
	}
	
	public String getName()
	{
		AssemName assemName = (AssemName)func;
		return assemName.getLabel().toString();
	}
	
	public AssemType translateToCJump(Label testLabel, Label endLabel)
	{
		// Move this value to a temporary
		Temp temp = new Temp();
		AssemTemp assemTemp = new AssemTemp(temp);
		AssemMove assemMov = new AssemMove(assemTemp, this);
		
		// Create a constant 0
		AssemConst assemConst0 = (AssemConst) new AssemConst(0);
		
		// Create a conditional based on whether the above temporary is 1 or 0
		AssemCJump tempCJump = new AssemCJump(AssemCJump.GT, assemTemp, assemConst0,
				testLabel, endLabel); 

		Stack seqStack = new Stack();
		seqStack.push(assemMov);
		seqStack.push(tempCJump);
		
		return translateSeqStatement(seqStack);
	}
	
	public AssemType translateToIntType()
	{
		// Create a new temporary to hold this constant
		Temp temp = new Temp();
		AssemTemp assemTemp = new AssemTemp(temp);
		
		// Move this value into the temporary and return the seq-exp
		AssemMove assemMove = new AssemMove(assemTemp, this);
		
		return new AssemSeqExp(assemMove, assemTemp);

	}
	
	public String getDescription()
	{
		return "call (" + func.getDescription() + ")";
	}
	
	public AssemExp build(AssemExpList list)
	{
		//return new MipsAssemCallExp(list.getHead(), preCallStmStack, postCallStmStack,
		//		list.getTail(), hasReturnValue);
		
		return new AssemCallExp(list.getHead(), list.getTail(), hasReturnValue);
	}
	
	public AssemSeqExp canonize()
	{
		// Canonize the arguments first
		Vector expStms = new Vector();
		for(AssemExpList tail = args; tail != null; tail = tail.getTail())
		{
			AssemSeqExp seqExp = tail.getHead().canonize();
			expStms.addElement(seqExp);
		}
		
		StmExpList stmExpList = getStmExpList(expStms);
		AssemStm stm = stmExpList.getStm();
		AssemExpList newArgs = stmExpList.getExps();
		
		// Add the statements for moving the expressions into $a0 - $a3 or onto to the stack
		// if there are more than four arguments
		Stack expArgStack = new Stack();
		int count = 0;
		for(AssemExpList tail = newArgs; tail != null; tail = tail.getTail())
		{
			AssemExp exp = tail.getHead();
			if(count < 4)
			{
				expArgStack.push(translateMove(translateTemp(Temp.getTemp("a" + count)), exp));
			}
			else
			{
				int offSetFromSP = (4 -count) * 4;
				Temp tempSP = Temp.getTemp("SP");
				AssemBinOpExp spAndOffSet = (AssemBinOpExp)translateBinOp(AssemBinOpExp.PLUS, 
						translateTemp(tempSP), translateConstant(offSetFromSP));
				expArgStack.push(translateMove(exp, spAndOffSet));
			}
			count++;
		}
		
		
		AssemSeq initSeq = translateSeqStatement(expArgStack);
		stm = new AssemSeq(stm, initSeq);
		
		// Canonize the function itself
		Temp temp = new Temp();
		AssemTemp assemTemp = new AssemTemp(temp);
		
		// Move this value into the temporary and return the seq-exp
		AssemMove assemMove = new AssemMove(assemTemp, 
				new AssemCallExp(func, newArgs, hasReturnValue));
		
		// Final canonize
		AssemSeqExp retValue = new AssemSeqExp(new AssemSeq(stm, assemMove),
									assemTemp);
		return retValue;
	}
	
	public AssemSeq translateSeqStatement(Stack seq)
	{
		AssemSeq retAssemType = null;
		
		while(!seq.isEmpty())
		{
			AssemType temp = (AssemType)seq.pop();
			// If there is an expression in this sequence, coerce it to a statement,
			// since we are building a list of statements 
			if(temp instanceof AssemExp)
			{
				AssemExp exp = (AssemExp) temp;
				temp = exp.translateToStatement();
			}
			retAssemType  = new AssemSeq((AssemStm)temp, (AssemStm)retAssemType);
		}
		
		return retAssemType;
	}
	
	private StmExpList getStmExpList(Vector expStms)
	{
		int numArgs = expStms.size();
		
		AssemSeq seqStm = null;
		Stack expStack = new Stack();
		for(int i = 0; i < numArgs; i++)
		{
			AssemSeqExp seqExp = (AssemSeqExp) expStms.elementAt(i);
			AssemStm stm = seqExp.getStm();
			AssemExp exp = seqExp.getExp();
			if(seqStm == null)
			{
				// Entered loop for the first time
				seqStm = new AssemSeq(stm, null);
			}
			else
			{
				// Check if the current stm commutes with the previous exp
				// Get the previous exp
				AssemExp prevExp = (AssemExp)expStack.pop();
				if(commutes(stm, prevExp))
				{
					seqStm = new AssemSeq(seqStm, stm);
				}
				else
				{
					Temp temp = new Temp();
					AssemTemp assemTemp = new AssemTemp(temp);
					AssemMove moveStm = new AssemMove(assemTemp, prevExp);
					seqStm = new AssemSeq(seqStm, new AssemSeq(moveStm, stm));
					prevExp = assemTemp;
				}
				//Push back the prev. exp
				expStack.push(prevExp);
			}
			expStack.push(exp);
		}
		
		// Create the expList from the stack created in the loop above
		AssemExpList expList = null;
		while(!expStack.isEmpty())
		{
			AssemExp exp = (AssemExp) expStack.pop();
			expList = new AssemExpList(exp, expList);
		}
		
		StmExpList stmExpList = new StmExpList(seqStm, expList);
		return stmExpList;
	}
	
	public boolean commutes(AssemStm stm, AssemExp exp)
	{
		return (stm == null) || (exp instanceof AssemName) || (exp instanceof AssemConst);
	}
	
	public int getExpType()
	{
		return AssemExp.CALL;
	}

	//public Stack getPostCallStmStack() {
	//	return postCallStmStack;
	//}
	
//	 TODO - these functions exist in TranslatingMediator too ... how to get rid of it?
	public AssemType translateMove(AssemType dst, AssemType src)
	{
		return new AssemMove((AssemExp)dst, (AssemExp)src);
	}
	
	public AssemType translateTemp(Temp temp)
	{
		return new AssemTemp(temp);
	}
	
	public AssemType translateConstant(int i)
	{
		return new AssemConst(i);
	}
	
	public AssemType translateBinOp(int operType, AssemType assemTypeLeft, AssemType assemTypeRight)
	{
		return new AssemBinOpExp(operType, (AssemExp) assemTypeLeft,
									(AssemExp)  assemTypeRight);
	}
	
}

class StmExpList
{
	private AssemStm stm;
	private AssemExpList exps;
	
	public StmExpList(AssemStm stm, AssemExpList exps)
	{
		this.stm = stm;
		this.exps = exps;
	}

	public AssemExpList getExps() {
		return exps;
	}

	public void setExps(AssemExpList exps) {
		this.exps = exps;
	}

	public AssemStm getStm() {
		return stm;
	}

	public void setStm(AssemStm stm) {
		this.stm = stm;
	}
}
